<%
  const type = locals.type;
  const layoutProperties = locals.layoutProperties;
  const paintProperties = locals.paintProperties;
  const allProperties = paintProperties.concat(layoutProperties);

  // Define the mapping from capability keys to struct member names
  const memberMap = {
    'Source': 'source',
    'Pass3D': 'pass3d',
    'Layout': 'layout',
    'FadingTiles': 'fadingTiles',
    'CrossTileIndex': 'crossTileIndex',
    'TileKind': 'tileKind'
  };
-%>
// clang-format off

// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.

#include <mbgl/style/layers/<%- type.replace(/-/g, '_') %>_layer.hpp>
#include <mbgl/style/layers/<%- type.replace(/-/g, '_') %>_layer_impl.hpp>
#include <mbgl/style/layer_observer.hpp>
#include <mbgl/style/conversion/color_ramp_property_value.hpp>
#include <mbgl/style/conversion/constant.hpp>
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/style/conversion/transition_options.hpp>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mbgl/util/traits.hpp>

#include <mapbox/eternal.hpp>

namespace mbgl {
namespace style {

<%
let layerCapabilities = {};
let defaults = { caps: { 'Source':   'NotRequired',
                         'Pass3D':   'NotRequired',
                         'Layout':   'NotRequired',
                         'FadingTiles': 'NotRequired',
                         'CrossTileIndex': 'NotRequired',
                         'TileKind' : 'NotRequired'
                 },
                 require: function(cap) {
                            let copy = Object.assign({}, this);
                            copy.caps = Object.assign({}, this.caps);
                            copy.caps[cap] = 'Required';
                            return copy;
                 },
                 set: function(cap, value) {
                            let copy = Object.assign({}, this);
                            copy.caps = Object.assign({}, this.caps);
                            copy.caps[cap] = value;
                            return copy;
                 },
                 finalize: function() {
                    return Object.keys(this.caps).reduce((acc, key) => {
                        acc.push(`${key}::${this.caps[key]}`);
                        return acc;
                    }, []);
                 }
               };

layerCapabilities['background']     = defaults.finalize();
layerCapabilities['fill']           = defaults.require('Source')
                                              .require('Layout')
                                              .set('TileKind', 'Geometry')
                                              .finalize();
layerCapabilities['fill-extrusion'] = defaults.require('Source')
                                              .require('Pass3D')
                                              .require('Layout')
                                              .set('TileKind', 'Geometry')
                                              .finalize();
layerCapabilities['hillshade']      = defaults.require('Source')
                                              .require('Pass3D')
                                              .set('TileKind', 'RasterDEM')
                                              .finalize();
layerCapabilities['symbol']         = defaults.require('Source')
                                              .require('Layout')
                                              .require('FadingTiles')
                                              .require('CrossTileIndex')
                                              .set('TileKind', 'Geometry')
                                              .finalize();
layerCapabilities['circle']         = defaults.require('Source')
                                              .require('Layout')
                                              .set('TileKind', 'Geometry')
                                              .finalize();
layerCapabilities['line']           = layerCapabilities['fill'];
layerCapabilities['heatmap']        = defaults.require('Source')
                                              .require('Pass3D')
                                              .set('TileKind', 'Geometry')
                                              .finalize();
layerCapabilities['raster']         = defaults.require('Source').set('TileKind', 'Raster').finalize();
layerCapabilities['location-indicator']     = defaults.finalize();

// Splits lines that are over 120 characters at the firts occurance of '='.
const split120Line = line => {
    const ident = '           ';
    if (line.length + ident.length > 120) {
        return line.replace('=', `=\n${ident}`);
    }
    return line;
};

%>
// static
const LayerTypeInfo* <%- camelize(type) %>Layer::Impl::staticTypeInfo() noexcept {
    const static LayerTypeInfo typeInfo{.type="<%- type %>",
                                        <%- layerCapabilities[type].map(cap => {
                                            const key = cap.split('::')[0];
                                            return `.${memberMap[key]}=LayerTypeInfo::${cap}`;
                                        }).join(',\n                                        ') %>};
    return &typeInfo;
}

<% if ((type === 'background') || (type === 'location-indicator')) { -%>
<%- camelize(type) %>Layer::<%- camelize(type) %>Layer(const std::string& layerID)
    : Layer(makeMutable<Impl>(layerID, std::string())) {
}
<% } else { -%>
<%- camelize(type) %>Layer::<%- camelize(type) %>Layer(const std::string& layerID, const std::string& sourceID)
    : Layer(makeMutable<Impl>(layerID, sourceID)) {
}
<% } -%>

<%- camelize(type) %>Layer::<%- camelize(type) %>Layer(Immutable<Impl> impl_)
    : Layer(std::move(impl_)) {
}

<%- camelize(type) %>Layer::~<%- camelize(type) %>Layer() {
    weakFactory.invalidateWeakPtrs();
}

const <%- camelize(type) %>Layer::Impl& <%- camelize(type) %>Layer::impl() const {
    return static_cast<const Impl&>(*baseImpl);
}

Mutable<<%- camelize(type) %>Layer::Impl> <%- camelize(type) %>Layer::mutableImpl() const {
    return makeMutable<Impl>(impl());
}

std::unique_ptr<Layer> <%- camelize(type) %>Layer::cloneRef(const std::string& id_) const {
    auto impl_ = mutableImpl();
    impl_->id = id_;
    impl_->paint = <%- camelize(type) %>PaintProperties::Transitionable();
    return std::make_unique<<%- camelize(type) %>Layer>(std::move(impl_));
}

<% if (layoutProperties.length) { -%>
void <%- camelize(type) %>Layer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>& writer) const {
    layout.stringify(writer);
}
<% } else { -%>
void <%- camelize(type) %>Layer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const {
}
<% } -%>

// Layout properties

<% for (const property of layoutProperties) { -%>
<%- propertyValueType(property) %> <%- camelize(type) %>Layer::getDefault<%- camelize(property.name) %>() {
    return <%- camelize(property.name) %>::defaultValue();
}

const <%- propertyValueType(property) %>& <%- camelize(type) %>Layer::get<%- camelize(property.name) %>() const {
    return impl().layout.get<<%- camelize(property.name) %>>();
}

void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>(const <%- propertyValueType(property) %>& value) {
    if (value == get<%- camelize(property.name) %>()) return;
    auto impl_ = mutableImpl();
    impl_->layout.get<<%- camelize(property.name) %>>() = value;
    baseImpl = std::move(impl_);
    observer->onLayerChanged(*this);
}
<% } -%>

// Paint properties
<% for (const property of paintProperties) { %>
<%- propertyValueType(property) %> <%- camelize(type) %>Layer::getDefault<%- camelize(property.name) %>() {
<% if (property.name === 'heatmap-color') { -%>
    conversion::Error error;
    std::string rawValue = R"JSON(<%- JSON.stringify(property.default) %>)JSON";
    return *conversion::convertJSON<<%- propertyValueType(property)%>>(rawValue, error);
<% } else { -%>
    return {<%- defaultValue(property) %>};
<% } -%>
}

const <%- propertyValueType(property) %>& <%- camelize(type) %>Layer::get<%- camelize(property.name) %>() const {
    return impl().paint.template get<<%- camelize(property.name) %>>().value;
}

void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>(const <%- propertyValueType(property) %>& value) {
    if (value == get<%- camelize(property.name) %>())
        return;
    auto impl_ = mutableImpl();
    impl_->paint.template get<<%- camelize(property.name) %>>().value = value;
<% if (property.name === 'line-width') { -%>
    impl_->paint.template get<LineFloorWidth>().value = value;
<% } -%>
    baseImpl = std::move(impl_);
    observer->onLayerChanged(*this);
}

void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>Transition(const TransitionOptions& options) {
    auto impl_ = mutableImpl();
    impl_->paint.template get<<%- camelize(property.name) %>>().options = options;
    baseImpl = std::move(impl_);
}

TransitionOptions <%- camelize(type) %>Layer::get<%- camelize(property.name) %>Transition() const {
    return impl().paint.template get<<%- camelize(property.name) %>>().options;
}
<% } -%>

using namespace conversion;

namespace {

constexpr uint8_t kPaintPropertyCount = <%- paintProperties.length * 2 -%>u;

enum class Property : uint8_t {
<% for (const property of paintProperties) { -%>
    <%- camelize(property.name) %>,
<% } -%>
<% for (const property of paintProperties) { -%>
    <%- camelize(property.name) %>Transition,
<% } -%>
<% for (let i = 0; i < layoutProperties.length; ++i) { -%>
<% if (i===0) { -%>
    <%- camelize(layoutProperties[i].name) %> = kPaintPropertyCount,
<% } else { -%>
    <%- camelize(layoutProperties[i].name) %>,
<% } -%>
<% } -%>
};

template <typename T>
constexpr uint8_t toUint8(T t) noexcept {
    return uint8_t(mbgl::underlying_type(t));
}

constexpr const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
    {<%- paintProperties.map(p => `{"${p.name}", toUint8(Property::${camelize(p.name)})}`).join(',\n     ') %>,
<% if (!layoutProperties.length) { -%>
     <%- paintProperties.map(p => `{"${p.name}-transition", toUint8(Property::${camelize(p.name)}Transition)}`).join(',\n     ') %>});
<% } else { -%>
     <%- paintProperties.map(p => `{"${p.name}-transition", toUint8(Property::${camelize(p.name)}Transition)}`).join(',\n     ') %>,
     <%- layoutProperties.map(p => `{"${p.name}", toUint8(Property::${camelize(p.name)})}`).join(',\n     ') %>});
<% } -%>

StyleProperty getLayerProperty(const <%- camelize(type) %>Layer& layer, Property property) {
    switch (property) {
<% for (const property of paintProperties) { -%>
        case Property::<%- camelize(property.name) %>:
            return makeStyleProperty(layer.get<%- camelize(property.name) %>());
<% } -%>
<% for (const property of paintProperties) { -%>
        case Property::<%- camelize(property.name) %>Transition:
            return makeStyleProperty(layer.get<%- camelize(property.name) %>Transition());
<% } -%>
<% for (const property of layoutProperties) { -%>
        case Property::<%- camelize(property.name) %>:
            return makeStyleProperty(layer.get<%- camelize(property.name) %>());
<% } -%>
    }
    return {};
}

StyleProperty getLayerProperty(const <%- camelize(type) -%>Layer& layer, const std::string& name) {
    const auto it = layerProperties.find(name.c_str());
    if (it == layerProperties.end()) {
        return {};
    }
    return getLayerProperty(layer, static_cast<Property>(it->second));
}

} // namespace

Value <%- camelize(type) %>Layer::serialize() const {
    auto result = Layer::serialize();
    assert(result.getObject());
    for (const auto& property : layerProperties) {
        auto styleProperty = getLayerProperty(*this, static_cast<Property>(property.second));
        if (styleProperty.getKind() == StyleProperty::Kind::Undefined) continue;
        serializeProperty(result, styleProperty, property.first.c_str(), property.second < kPaintPropertyCount);
    }
    return result;
}

std::optional<Error> <%- camelize(type) %>Layer::setPropertyInternal(const std::string& name, const Convertible& value) {
    const auto it = layerProperties.find(name.c_str());
    if (it == layerProperties.end()) return Error{"layer doesn't support this property"};

    auto property = static_cast<Property>(it->second);

<%
    const conversions = {};
    for (const property of allProperties) {
        const dataDriven = property['property-type'] === 'data-driven' || property['property-type'] === 'cross-faded-data-driven';
        const convertTokens = property.name === 'icon-image' || property.name === 'text-field';
        const conversion = `const auto& typedValue = convert<${propertyValueType(property)}>(value, error, ${dataDriven}, ${convertTokens})`;
        conversions[conversion] = conversions[conversion] || [];
        conversions[conversion].push(property);
    }
    const reduceConditions = (total, e) => {
        const splitter = ' || ';
        const ident = '        ';
        let totalLength = total.length + e.length + ident.length + splitter.length + 3 /* ||*/;
        const lastCR = total.lastIndexOf('\n');
        const length = (lastCR == -1) ? totalLength : (totalLength - (ident.length + lastCR));
        return total + (length <= 120 ? splitter : ` ||\n${ident}`) + e;
    }
-%>
<% for (const key in conversions) {
    const properties = conversions[key];
-%>
    if (<%- properties.map(p => `property == Property::${camelize(p.name)}`).reduce(reduceConditions) %>) {
        Error error;
        <%- split120Line(key) %>;
        if (!typedValue) {
            return error;
        }
<% if (properties.length == 1) { %>
        set<%- camelize(properties[0].name) %>(*typedValue);
        return std::nullopt;
<% } else for (const property of properties) { %>
        if (property == Property::<%- camelize(property.name) %>) {
            set<%- camelize(property.name) %>(*typedValue);
            return std::nullopt;
        }
<% } -%>
    }
<% } %>
    Error error;
    std::optional<TransitionOptions> transition = convert<TransitionOptions>(value, error);
    if (!transition) {
        return error;
    }
<% for (const property of paintProperties) { %>
    if (property == Property::<%- camelize(property.name) %>Transition) {
        set<%- camelize(property.name) %>Transition(*transition);
        return std::nullopt;
    }
<% } %>
    return Error{"layer doesn't support this property"};
}

StyleProperty <%- camelize(type) %>Layer::getProperty(const std::string& name) const {
    return getLayerProperty(*this, name);
}

Mutable<Layer::Impl> <%- camelize(type) %>Layer::mutableBaseImpl() const {
    return staticMutableCast<Layer::Impl>(mutableImpl());
}

} // namespace style
} // namespace mbgl

// clang-format on
